package com.colbert.jknife.writer;

import com.colbert.jknife.model.Constant;
import com.colbert.jknife.model.FieldCodeSet;
import com.colbert.jknife.model.ICodeSetManager;
import com.colbert.jknife.model.IInjectAdapter;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;

import java.util.Map;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;

public class JavaFileWriter implements IJavaFileWriter {

    private Filer filer;

    private Elements elements;

    private Messager messager;

    private JavaFileWriter(Filer filer, Messager messager, Elements elements) {
        this.filer = filer;
        this.messager = messager;
        this.elements = elements;
    }

    public static class Builder {

        private Filer filer;

        private Messager messager;

        private Elements elements;

        public Builder filer(Filer filer) {
            this.filer = filer;
            return this;
        }

        public Builder elements(Elements elements) {
            this.elements = elements;
            return this;
        }

        public Builder messager(Messager messager) {
            this.messager = messager;
            return this;
        }

        public JavaFileWriter build() {
            return new JavaFileWriter(filer, messager, elements);
        }
    }

    @Override
    public void generate(ICodeSetManager manager) throws Exception {
        for (Map.Entry<String, Map<Integer, FieldCodeSet>> entry : manager.getCodeSetMap().entrySet()) {
            TypeElement element = elements.getTypeElement(entry.getKey());
            TypeName parentTypeName = TypeName.get(element.asType());
            MethodSpec.Builder builder = MethodSpec.methodBuilder(Constant.INJECT)
                    .addModifiers(Modifier.PUBLIC)
                    .addParameter(parentTypeName, Constant.TARGET, Modifier.FINAL)
                    .addParameter(TypeName.OBJECT, Constant.PARENT_VIEW_LITERAL);
            builder.addStatement("$T $L", TypeName.get(elements.getTypeElement(Constant.CLASS_VIEW).asType()), Constant.VIEW_LITERAL);
            for (FieldCodeSet fieldCodeSet : entry.getValue().values()) {
                if (fieldCodeSet.getFieldCodeBlock() == null) {
                    return;
                }
                builder.addCode(fieldCodeSet.getFieldCodeBlock());
                builder.addCode(CodeBlock.of("\n"));
                if (fieldCodeSet.getMethodCodeBlocks() != null) {
                    for (CodeBlock codeBlock : fieldCodeSet.getMethodCodeBlocks()) {
                        builder.addCode(codeBlock);
                        builder.addCode(CodeBlock.of("\n"));
                    }
                }
            }

            TypeSpec typeSpec = TypeSpec.classBuilder(element.getSimpleName().toString() + Constant.CLASS_SUFFIX)
                    .addJavadoc(Constant.CLASS_JAVADOC)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(builder.build())
                    .addSuperinterface(ParameterizedTypeName.get(ClassName.get(IInjectAdapter.class), parentTypeName))
                    .build();
            PackageElement packageElement = elements.getPackageOf(element);
            String packageName = packageElement.isUnnamed() ? null : packageElement.getQualifiedName().toString();
            JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
        }
    }
}
